var JSS = {
	Version: 0.1,
	init: function(options) {
		options = options || {};
		this._onReadyCallbacks = [];
		if (typeof(options.onReady) == "function") {
			this.onReady(options.onReady);
		}
		options.disallowFlash = !!options.disallowFlash;
		
		if (this.DomStorage.isCompatible()) {
			this._provider = this.DomStorage.init();
		} else if (this.UserData.isCompatible()) {
			this._provider = this.UserData.init();
		} else if (this.OpenDatabase.isCompatible()) {
			this._provider = this.OpenDatabase.init();
		} else if (this.SharedObject.isCompatible() && !options.disallowFlash) {
			this._provider = this.SharedObject.init();
		} else {
			this._incompatible = true;
		}
	},
	
	set: function(key, value, callback) {
		try {
			if (!key || !this._provider) {
				return null;
			}
			this._provider.set(key, uneval(value), callback || function() {});
		} catch(e) {
			return false;
		} finally {
			key = value = null;
		}
	},
	get: function(key, callback) {
		try {
			if (!key || !this._provider) {
				return null;
			}
			return this._provider.get(key, function(response) {
				if (callback) {
						callback(eval(response));
				}
			});
		} catch(e) {
			return null;
		} finally {
			key = null;
		}
	},
	remove: function(key, callback) {
		try {
			if (!key || !this._provider) {
				return false;
			}
			return this._provider.remove(key, callback || function() {});
		} catch(e) {
			return false;
		} finally {
			key = null;
		}
	},
	clear: function(callback) {
		try {
			if (!this._provider) {
				return false;
			}
			return this._provider.clear(callback || function() {});
		} catch(e) {
			return false;
		}
	},
	isCompatible: function() {
		return !this._incompatible;
	}
};

// Firefox 2+, IE 8+
JSS.DomStorage = {
	init: function() {
		this._storage = window.sessionStorage;
		return this;
	},
	set: function(key, value, callback) {
		this._storage.setItem(key, value);
		callback();
		return true;
	},
	get: function(key, callback) {
		var item = this._storage.getItem(key);
		var value = item ? item.value : null;
		callback(value);
		return true;
	},
	remove: function(key, callback) {
		this._storage.removeItem(key);
		callback();
		return true;
	},
	clear: function(callback) {
		if (this._storage.clear) {
			this._storage.clear();
		} else {
			for (i in this._storage) {
				if (this._storage[i].value) {
					this.remove(i);
				}
			}
		}
		callback();
		return true;
	},
	isCompatible: function() {
		return !!window.sessionStorage;
	}
};

// IE 6+
JSS.UserData = {
	init: function() {
		this._callbackStorage = [];
		this._iframe = document.createElement("iframe");
		this._iframe.width = this._iframe.height = 0;
		this._iframe.style.display = "none";
		this._iframe.src = "/";
		(document.body || document.documentElement).appendChild(this._iframe);
		this._iframeWindow = this._iframe.contentWindow;
		this._iframeWindow.document.execCommand("stop");
		this["_readyStateControl_IE" + parseInt(this._ieVersion, false)]();
		return this;
	},
	_readyStateControl_IE7: function() {
		var that = this;
		this._iframe.onreadystatechange = function() {
			this.onreadystatechange = null;
			this.src = "javascript:document.write('<html id=\"wrapper\"></html>');";
			that._iframeWindow.document.close();
			that._appendStorageElement();
		};
	},
	_readyStateControl_IE6: function() {
		var firstState = false, that = this;
		this._iframe.onreadystatechange = function() {
			if (!firstState && this.readyState == "complete") {
				this.src = "javascript:document.open(); document.write('<html id=\"wrapper\"></html>'); document.close();";
				firstState = true;
			} else if (this.readyState == "complete") {
				this.onreadystatechange = null;
				that._appendStorageElement();
			}
		};
	},
	_appendStorageElement: function() {
		this._storage = this._iframeWindow.document.createElement("span");
		this._storage.style.behavior = "url(#default#userdata)";
		this._iframeWindow.document.getElementById("wrapper").appendChild(this._storage);
		this._storage.load("jss");
		var that = this;
		window.attachEvent("onunload", function() {
			that._storage.save("jss");
		});
		if (!JSS._Helper.LifeTimeCookie.isAvailable()) {
			this.clear();
			JSS._Helper.LifeTimeCookie.set();
		}
		this._initReady();
	},
	set: function(key, value, callback) {
		if (this._initialized) {
			this._storage.setAttribute(key, value);
			callback();
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [key, value, callback]); });
		}
		return true;
	},
	get: function(key, callback) {
		if (this._initialized) {
			callback(
				this._storage.getAttribute(key)
			);
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [key, callback]); });
		}
		return true;
	},
	remove: function(key, callback) {
		if (this._initialized) {
			this._storage.removeAttribute(key);
			callback();
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [key, callback]); });
		}
		return true;
	},
	clear: function(callback) {
		if (this._initialized) {
			var date = new Date();
			date.setMinutes(date.getMinutes() - 1);
			this._storage.expires = date.toUTCString();
			this._storage.save("jss");
			this._storage.load("jss");
			callback();
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [callback]); });
		}
		return true;
	},
	isCompatible: function() {
		if (navigator.userAgent.indexOf("MSIE") != -1) {
			this._ieVersion = parseFloat(navigator.appVersion.split("MSIE")[1]);
			return (this._ieVersion >= 6);
		}
		return false;
	},
	_initReady: function() {
		this._initialized = true;
		for (var i=0; i<this._callbackStorage.length; i++) {
			this._callbackStorage[i]();
		}
		delete this._callbackStorage;
	}
};

// All user agents with flash plugin >= 6
JSS.SharedObject = {
	init: function() {
		this._callbackStorage = [];
		console.log(12);
		var that = this;
		window.__jssCallback = function() { that._flashCallback(); };
		var wrapper = document.createElement("div");
		wrapper.style.position = "absolute";
		var swfHtml = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" width="1" height="1" id="SharedObjectProvider">'
			+ '<param name="movie" value="SharedObjectProvider.swf?jssCallback=window.__jssCallback" />'
			+ '<param name="quality" value="low" />'
			+ '<param name="wmode" value="transparent" />'
			+ '<param name="swliveconnect" value="true" />'
			+ '<param name="allowScriptAccess" value="sameDomain" />'
			+ '<embed src="SharedObjectProvider.swf?jssCallback=window.__jssCallback" quality="low" swLiveConnect="true" width="1" height="1" allowScriptAccess="sameDomain" wmode="transparent" name="SharedObjectProvider" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>'
			+ '</object>';
		(document.body || document.documentElement).appendChild(wrapper);
		wrapper.innerHTML = swfHtml;
		this._storage = document.SharedObjectProvider || document.getElementById("SharedObjectProvider");
		return this;
	},
	set: function(key, value, callback) {
		if (this._initialized) {
			this._storage.SetVariable("jssConnector." + key, value);
			this._storage.SetVariable("jssFetcher.changeKey", key);
			callback();
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [key, value, callback]); });
		}
		return true;
	},
	get: function(key, callback) {
		if (this._initialized) {
			callback(
				this._storage.GetVariable("jss_" + key)
			);
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [key, callback]); });
		}
	},
	remove: function(key, callback) {
		if (this._initialized) {
			this._storage.SetVariable("jssFetcher.removeKey", key);
			callback();
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [key, callback]); });
		}
		return true;
	},
	clear: function(callback) {
		if (this._initialized) {
			this._storage.SetVariable("jssFetcher.clearAll", 1);
			callback();
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [callback]); });
		}
		return true;
	},
	isCompatible: function() {
		var flashEnabled = false, flashVersion = 0;
		if (navigator.plugins && navigator.plugins.length > 0) {
			var mimeTypes = navigator.mimeTypes, type = "application/x-shockwave-flash";
			if (mimeTypes && mimeTypes[type] && mimeTypes[type].enabledPlugin && mimeTypes[type].enabledPlugin.description) {
				flashVersion = parseInt(mimeTypes[type].enabledPlugin.description.split(" ")[2], false);
				flashEnabled = true;
			}
		} else if (navigator.appVersion.indexOf("Mac") == -1 && window.execScript) {
			try {
				var flashObject = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
				flashEnabled = true;
				flashVersion = parseInt(flashObject.GetVariable("$version").replace(/[a-zA-Z]/g, ""), false);
			} catch(e) {
				try {
					var flashObject = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
					flashEnabled = true;
					flashVersion = 7;
				} catch(e) {
					try {
						var flashObject = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
						flashEnabled = true;
						flashVersion = 6;
					} catch(e) {}
				}
			}
		}
		return (flashEnabled && flashVersion >= 6);
	},
	_flashCallback: function() {
		if (!JSS._Helper.LifeTimeCookie.isAvailable()) {
			this.clear();
			JSS._Helper.LifeTimeCookie.set();
		}
		this._initReady();
	},
	_initReady: function() {
		this._initialized = true;
		for (var i=0; i<this._callbackStorage.length; i++) {
			this._callbackStorage[i]();
		}
		delete this._callbackStorage;
	}
};

// Safari (Version >= 3.1) openDatabase
JSS.OpenDatabase = {
	init: function() {
		this._callbackStorage = [];
		this._storage = window.openDatabase("JSS", "1.0", 65536, "JSS Database");
		this._createTable();
		if (!JSS._Helper.LifeTimeCookie.isAvailable()) {
			this.clear();
			JSS._Helper.LifeTimeCookie.set();
		}
		return this;
	},
	set: function(key, value, callback) {
		if (this._initialized) {
			this._storage.transaction(function(tx) {
				tx.executeSql("SELECT value FROM SessionStorage WHERE key = ?", [key], function(tx, result) {
					if (result.rows.length > 0) {
						tx.executeSql("UPDATE SessionStorage SET value = ? WHERE key = ?", [value, key], callback);
					} else {
						tx.executeSql("INSERT INTO SessionStorage (key, value) VALUES (?, ?)", [key, value], callback);
					}
				});
			});
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [key, value, callback]); });
		}
		return true;
	},
	get: function(key, callback) {
		if (this._initialized) {
			this._storage.transaction(function(tx) {
				tx.executeSql("SELECT value FROM SessionStorage WHERE key = ?", [key], function(tx, result) {
					callback(result.rows.item(0).value);
				});
			});
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [key, callback]); });
		}
		return true;
	},
	remove: function(key, callback) {
		if (this._initialized) {
			this._storage.transaction(function(tx) {
				tx.executeSql("DELETE FROM SessionStorage WHERE key = ?", [key], function() { callback(); });
			});
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [key, callback]); });
		}
		return true;
	},
	clear: function(callback) {
		if (this._initialized) {
			this._storage.transaction(function(tx) {
				tx.executeSql("DELETE FROM SessionStorage", [], callback);
			});
		} else {
			var self = arguments.callee, that = this;
			this._callbackStorage.push(function() { self.apply(that, [callback]); });
		}
		return true;
	},
	isCompatible: function() {
		return !!window.openDatabase;
	},
	_createTable: function() {
		var that = this;
		this._storage.transaction(function(tx) {
			tx.executeSql("SELECT * FROM SessionStorage", [], function(tx, result) {
				if (!JSS._Helper.LifeTimeCookie.isAvailable()) {
					that.clear();
					JSS._Helper.LifeTimeCookie.set();
				}
				that._initReady();
			}, function(tx) {
				tx.executeSql("CREATE TABLE SessionStorage (key TEXT, value TEXT)", [], function() {
					if (!JSS._Helper.LifeTimeCookie.isAvailable()) {
						JSS._Helper.LifeTimeCookie.set();
					}
					that._initReady();
				});
			});
		});
	},
	"_initReady": function() {
		this._initialized = true;
		for (var i=0; i<this._callbackStorage.length; i++) {
			this._callbackStorage[i]();
		}
		delete this._callbackStorage;
	}
};

JSS._Helper = {};
JSS._Helper.LifeTimeCookie = {
	isAvailable: function() {
		return (document.cookie.indexOf("__jssId") != -1) ? true : false;
	},
	set: function() {
		document.cookie = "__jssId=1";
		return true;
	}
};

JSS._Helper.uneval = function(object) {
	if (typeof(object.toSource) == "function") {
		return object.toSource();
	}
	
	var output;
	switch(typeof(object)) {
		case "undefined":
			output = "undefined";
			break;
		case "string":
			output = "unescape('" + escape(object) + "')";
			break;
		case "object":
			if (object == null) {
				output = "null";
			} else if (typeof(object.test) == "function") {
				output = object.toString();
			} else if (typeof(object.getTime) == "function") {
				output = "new Date(" + object.getTime() + ")";
			} else if (typeof(object.substring) == "function") {
				output = "new String(unescape('" + escape(object.valueOf()) + "'))";
			} else if (object.constructor.toString().indexOf("Array") != -1) {
				var outputArray = [];
				for (var i=0; i<object.length; i++) {
					outputArray.push(uneval(object[i]));
				}
				output = "[" + outputArray.join(",") + "]";
			} else {
				var outputArray = [];
				for (var i in object) {
					outputArray.push(i + ":" + uneval(object[i])); 
				}
				output = "{" + outputArray.join(",") + "}";
			}
			break;
		default:
			output = object.toString ? object.toString() : (object + "");
			break;
	}
	return "(" + output + ")";
};

if (!window.uneval) {
	var uneval = JSS._Helper.uneval;
}